﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "UiConfiguration.h"

#include "ICommandButtonDefinition.h"
#include "ICommandButtonMenuDefinition.h"
#include "ICommandMenuItemDefinition.h"
#include "IRibbonPage.h"
#include "IRibbon.h"
#include "IMainWindow.h"
#include "IRibbonGroup.h"
#include "IPageHandler.h"
#include "IUiDocumentViewManager.h"
#include "IUiDocument.h"
#include "IDocument.h"

#include <QtXml/QDomDocument>
#include <QtXml/QDomElement>
#include <QtCore/QFile>


using namespace gcmp;

namespace Sample
{

    class ProjectVisiblePageHandler :public  IPageHandler
    {
    public:
        bool IsVisible() const override {
            auto manager = IUiDocumentViewManager::Get();
            if (manager) {
                auto uiDoc = manager->GetCurrentUiDocument();
                if (uiDoc) {
                    auto doc = uiDoc->GetDbDocument();
                    if (doc && !doc->IsFamilyDocument()) {
                        return true;
                    }
                }
            }
            return false;
        }
        bool IsEnabled()const override { return true; }
    };

    enum class ToolbarName
    {
        UnKnown = -1,
        MainToolbar,
        MainTitlebar,
        BottomToolbar,
        AssistantPanel
    };

    class RibbonUi {
    public:
        static RibbonUi& Get() {
            static RibbonUi object;
            return object;
        }

        void AddCommandButton(
            CommandButton button
        );

        gcmp::IRibbonPage* FindPage(const std::wstring& key);
        gcmp::IRibbonPage* AddPage(const std::wstring& key, const std::wstring& caption);

        CommandButton  FindButtonDefinition(const std::wstring& key);
    private:
        RibbonUi() = default;
    private:
        std::map<std::wstring, CommandButton> m_commandButtons{};
        std::map<std::wstring, gcmp::IRibbonPage*> m_pages{};
    };

    void RibbonUi::AddCommandButton(CommandButton button)
    {
        m_commandButtons[button.key] = button;
    }

    gcmp::IRibbonPage* RibbonUi::FindPage(const std::wstring& key)
    {
        auto it = m_pages.find(key);
        if (it != m_pages.end()) {
            return it->second;
        }
        gcmp::IMainWindow* pMainWindow = IMainWindow::GetMainWindow();
        if (pMainWindow) {
            gcmp::IRibbon* pRibbon = pMainWindow->GetRibbon();
            if (pRibbon) {
                for (auto page : pRibbon->GetAllPages()) {
                    if (page) {
                        if (page->GetPageId() == key) {
                            m_pages[key] = page;
                            return page;
                        }
                    }
                }
            }
        }
        return nullptr;
    }

    gcmp::IRibbonPage* RibbonUi::AddPage(const std::wstring& key, const std::wstring& caption)
    {
        gcmp::IRibbonPage* result = nullptr;
        gcmp::OwnerPtr<gcmp::IRibbonPage> opPage = IRibbonPage::Create(key, caption);
        if (opPage == nullptr) {
            return nullptr;
        }
        opPage->SetPageHandler(NEW_AS_OWNER_PTR(ProjectVisiblePageHandler));
        m_pages[key] = opPage.get();
        result = opPage.get();
        gcmp::IMainWindow* pMainWindow = IMainWindow::GetMainWindow();
        if (pMainWindow) {
            gcmp::IRibbon* pRibbon = pMainWindow->GetRibbon();
            if (pRibbon) {
                pRibbon->AddPage(std::move(opPage));
            }
        }
        return result;
    }

    CommandButton RibbonUi::FindButtonDefinition(const std::wstring& key)
    {
        auto it = m_commandButtons.find(key);
        if (it == m_commandButtons.end()) {
            return {};
        }
        CommandButton result = it->second;
        //这里直接返回正确的图标路径，以避免多处修改
        result.iconDirectory = result.iconDirectory + L"/48x48.png";
        return result;
    }

    class UiConfiguration::Impl
    {
    public:
        explicit Impl(const std::wstring& file) :m_file(file) {};
        void SetupUi();
        void LoadDefinitions();
    private:
        void LoadUiImpl(bool loadUiOrDefinitions);
        void LoadControlDefinitions(QDomElement element);
        void LoadButtonDefinition(QDomElement element);
        void LoadDropdownButtonDefinition(QDomElement element);

        void LoadToolbar(QDomElement element);
        void LoadWorkspaceTab(QDomElement element);
        void LoadBlock(QDomElement element, gcmp::IRibbonPage* pPage);
        void LoadDropdownMenu(QDomElement element, gcmp::IRibbonGroup* pGroup);

        template<typename Fn>
        void VisitChildren(QDomElement parent, const char* tagName, Fn fn)
        {
            if (parent.isNull()) return;
            QDomNodeList children = parent.elementsByTagName(tagName);
            for (int i = 0; i < children.count(); i++) {
                QDomNode node = children.at(i);
                if (node.parentNode() != parent)
                    continue;
                if (node.isElement()) {
                    fn(node.toElement());
                }
            }
        }
    private:
        std::wstring m_file;
    };

    UiConfiguration::UiConfiguration(const std::wstring& uifile)
        :m_opImpl(NEW_AS_OWNER_PTR(Impl, uifile))
    {
        if (m_opImpl) {
            m_opImpl->LoadDefinitions();
        }
    }

    UiConfiguration::~UiConfiguration() = default;

    void UiConfiguration::SetupUi()
    {
        DBG_WARN_AND_RETURN_VOID_UNLESS(m_opImpl, L"断言`m_opImpl`失败",L"GDMPLab",L"2024-03-30");
        m_opImpl->SetupUi();
    }

    void UiConfiguration::Impl::SetupUi()
    {
        LoadUiImpl(true);
    }

    void UiConfiguration::Impl::LoadDefinitions()
    {
        LoadUiImpl(false);
    }

    void UiConfiguration::Impl::LoadUiImpl(bool loadUiOrDefinitions)
    {
        QFile file(QString::fromStdWString(m_file));
        bool bRet = file.open(QFile::ReadOnly | QFile::Text);
        std::wstring errorTips = QString::fromStdWString(L"打开界面配置文件%1失败").arg(QString::fromStdWString(m_file)).toStdWString();
        DBG_WARN_AND_RETURN_VOID_UNLESS(bRet, errorTips,L"GDMPLab",L"2024-03-30");

        QDomDocument doc{};
        QString errorMsg{};
        int errorLine{};
        int errorColumn{};
        bRet = doc.setContent(&file, &errorMsg, &errorLine, &errorColumn);
        if (!bRet) {
            QStringList tips;
            tips << QString::fromStdWString(L"文件(")
                << QString::fromStdWString(m_file)
                << QString::fromStdWString(L")解析失败,原因为:")
                << errorMsg
                << QString::fromStdWString(L"，出错位置:行-")
                << QString::number(errorLine)
                << QString::fromStdWString(L"，列-")
                << QString::number(errorColumn)
                << QString::fromStdWString(L"。");
            errorTips = tips.join("").toStdWString();
        }
        DBG_WARN_AND_RETURN_VOID_UNLESS(bRet, errorTips,L"GDMPLab",L"2024-03-30");

        QDomElement root = doc.documentElement();
        if (loadUiOrDefinitions) {
            VisitChildren(root, "Toolbar",
                [&](QDomElement e) {
                    LoadToolbar(e);
                });
        }
        else
        {
            LoadControlDefinitions(root.firstChildElement("ControlDefinitions"));
        }
    }

    void UiConfiguration::Impl::LoadControlDefinitions(QDomElement element)
    {
        if (element.isNull()) return;

        {
            VisitChildren(element, "ButtonDefinitions",
                [&](QDomElement parent) {
                    VisitChildren(parent, "ButtonDefinition",
                    [&](QDomElement e) { LoadButtonDefinition(e); }
            );
                });
        }
        {
            VisitChildren(element, "DropdownMenuDefinitions",
                [&](QDomElement parent) {
                    VisitChildren(parent, "DropdownMenuDefinition",
                    [&](QDomElement e) { LoadDropdownButtonDefinition(e); }
            );
                });
        }
    }

    namespace
    {
        void XmlAttrTo(const QString& value, bool& v) {
            v = (value == "True");
        }
        void XmlAttrTo(const QString& value, int& v) {
            bool ok{ false };
            int iV = value.toInt(&ok);
            if (ok) {
                v = iV;
            }
        }
        void XmlAttrTo(const QString& value, std::wstring& v) {
            v = value.toStdWString();
        }

        template<typename T>
        void GetXmlAttribute(QDomElement const& e, const char* key, T& v) {
            if (!e.hasAttribute(key)) return;
            QString value = e.attribute(key);
            XmlAttrTo(e.attribute(key), v);
        }
    }

    void UiConfiguration::Impl::LoadButtonDefinition(QDomElement element)
    {
        if (element.isNull()) return;
        if (!element.hasAttribute("ID")) return;

        std::wstring id{};
        bool interactive{ false };
        bool isDropDownButton{ false };
        std::wstring caption{};
        std::wstring tooltip{};
        std::wstring iconDir{};

        GetXmlAttribute(element, "ID", id);
        GetXmlAttribute(element, "Interactive", interactive);
        GetXmlAttribute(element, "IsDropDownButton", isDropDownButton);

        GetXmlAttribute(element, "Caption", caption);
        GetXmlAttribute(element, "Tooltip", tooltip);
        GetXmlAttribute(element, "Icon", iconDir);

        RibbonUi::Get().AddCommandButton(
            CommandButton{
                id,
                caption,
                tooltip,
                iconDir
            }
        );


        //if (id == L"Gdmp.CommandId.EnableTemporaryWorkPlane" ||
        //    id == L"Gdmp.CommandId.EnableSnap3dTemporaryWorkPlane" ||
        //    id == L"Gdmp.CommandId.PolygonPick.Activate" ||
        //    id == L"Gdmp.CommandId.ShowLineWidth" ||
        //    id == L"Gdmp.CommandId.OrthogonalPattern") {
        //    m_buttons.insert({ id.c_str(), KeyToCommandButton{ caption.c_str(), tooltip.c_str(), (iconDir + L"/32x32.png").c_str() } });
        //}
        //else {
        //    m_buttons.insert({ id.c_str(), KeyToCommandButton{ caption.c_str(), tooltip.c_str(), (iconDir + L"/48x48.png").c_str() } });
        //}
    }

    void UiConfiguration::Impl::LoadDropdownButtonDefinition(QDomElement element)
    {
        if (element.isNull()) return;
        if (!element.hasAttribute("ID")) return;

        std::wstring id{};
        std::wstring caption{};
        std::wstring tooltip{};
        std::wstring iconDir{};

        GetXmlAttribute(element, "ID", id);

        GetXmlAttribute(element, "Caption", caption);
        GetXmlAttribute(element, "Tooltip", tooltip);
        GetXmlAttribute(element, "Icon", iconDir);

        RibbonUi::Get().AddCommandButton(
            CommandButton{
                id,
                caption,
                tooltip,
                iconDir
            }
        );
    }

    void UiConfiguration::Impl::LoadToolbar(QDomElement element)
    {
        if (element.isNull()) return;
        std::wstring key{};
        GetXmlAttribute(element, "Key", key);
        ToolbarName toolbarName = ToolbarName::UnKnown;
        if (key == L"MainToolbar") {
            toolbarName = ToolbarName::MainToolbar;
        }
        else if (key == L"MainTitleBar")
        {
            toolbarName = ToolbarName::MainTitlebar;
        }
        else if (key == L"MainTitlebar")
        {
            toolbarName = ToolbarName::MainTitlebar;
        }
        else if (key == L"BottomToolbar")
        {
            toolbarName = ToolbarName::BottomToolbar;
        }
        else if (key == L"AssistantPanel")
        {
            toolbarName = ToolbarName::AssistantPanel;
        }

        if (toolbarName == ToolbarName::MainTitlebar)
        {
            QDomElement parent = element.firstChildElement("WorkspaceTabBar");
            VisitChildren(parent, "WorkspaceTab", [&](QDomElement e) {
                LoadWorkspaceTab(e);
                });
        }

        std::wstring pageKey = L"";
        std::wstring pageName = L"";
        //注意,由于Qt界面框架上暂无这几个工具栏区域,这里在Ribbon上创建相应页来表达
        switch (toolbarName)
        {
        case ToolbarName::MainTitlebar:
        {
            pageKey = L"Sample::MainTitleBar";
            pageName = L"示例标题栏";
        }
        break;
        case ToolbarName::BottomToolbar:
        {
            pageKey = L"Sample::BottomToolbar";
            pageName = L"示例底部工具栏";
        }
        break;
        case ToolbarName::AssistantPanel:
        {
            pageKey = L"Sample::AssistantPanel";
            pageName = L"示例辅助面板";
        }
        break;
        default:
            break;
        }

        gcmp::IRibbonPage* pRibbonPage{};
        if (!pageKey.empty()) {
            pRibbonPage = RibbonUi::Get().FindPage(pageKey);
            if (pRibbonPage == nullptr) {
                pRibbonPage = RibbonUi::Get().AddPage(pageKey, pageName);
            }
        }

        if (toolbarName != ToolbarName::UnKnown) {
            VisitChildren(element, "Block", [&](QDomElement e) {
                LoadBlock(e, pRibbonPage);
                });
        }
    }

    void UiConfiguration::Impl::LoadWorkspaceTab(QDomElement element)
    {
        if (element.isNull()) return;
        std::wstring caption{};
        std::wstring key{};
        GetXmlAttribute(element, "Caption", caption);
        GetXmlAttribute(element, "Key", key);
        if (key.empty())
        {
            return;
        }

        IRibbonPage* pRibbonPage = RibbonUi::Get().FindPage(key);
        if (!pRibbonPage) {
            RibbonUi::Get().AddPage(key, caption);
        }
    }

    void UiConfiguration::Impl::LoadBlock(QDomElement element, gcmp::IRibbonPage* pPage)
    {
        std::wstring workspace{};
        GetXmlAttribute(element, "Workspace", workspace);

        //根据工作区查找所在页，如果没有就接收传递过来的
        gcmp::IRibbonPage* pRibbonPage = RibbonUi::Get().FindPage(workspace);
        if (pRibbonPage == nullptr) {
            pRibbonPage = pPage;
        }
        if (!pRibbonPage) {
            return;
        }

        std::wstring name{};
        GetXmlAttribute(element, "Name", name);
        if (name.empty()) {
            name = L"未命名分组";
        }
        static int index{ 0 };
        std::wstring groupKey = L"RibbonGroup" + std::to_wstring(index++);

        gcmp::OwnerPtr<gcmp::IRibbonGroup> opRibbonGroup = IRibbonGroup::Create(groupKey, name);

        QDomNodeList children = element.childNodes();
        for (int i = 0; i < children.count(); i++) {
            QDomNode node = children.at(i);
            if (!node.isElement()) {
                continue;
            }
            QDomElement child = node.toElement();
            if (child.tagName() == "Button") {
                std::wstring id{};
                bool hasText{ true };
                GetXmlAttribute(child, "ID", id);
                GetXmlAttribute(child, "HasText", hasText);

                auto buttonDef = RibbonUi::Get().FindButtonDefinition(id);
                if (buttonDef.key == id) {
                    opRibbonGroup->AddButton(ICommandButtonDefinition::Create(id.c_str(),
                        buttonDef.caption, buttonDef.tooltip, id.c_str(), buttonDef.iconDirectory)
                    );
                }
            }
            else if (child.tagName() == "DropdownMenu")
            {
                LoadDropdownMenu(child, opRibbonGroup.get());
            }
        }

        DBG_WARN_AND_RETURN_VOID_UNLESS(opRibbonGroup, L"断言`opRibbonGroup`失败",L"GDMPLab",L"2024-03-30");
        pRibbonPage->AddGroup(std::move(opRibbonGroup));
    }

    void UiConfiguration::Impl::LoadDropdownMenu(QDomElement element, gcmp::IRibbonGroup* pGroup)
    {
        if (element.isNull()) return;
        std::wstring id{};
        int currentIndex{};
        GetXmlAttribute(element, "ID", id);
        GetXmlAttribute(element, "CurrentIndex", currentIndex);

        // 创建下拉菜单并将其添加至group
        auto menuDef = RibbonUi::Get().FindButtonDefinition(id);
        if (menuDef.key != id) {
            return;
        }

        OwnerPtr<ICommandButtonMenuDefinition> opMenuButton;

        QDomNodeList children = element.childNodes();
        for (int i = 0; i < children.count(); i++) {
            QDomNode node = children.at(i);
            if (!node.isElement()) {
                continue;
            }
            QDomElement child = node.toElement();
            if (child.tagName() == "DropdownMenuEntry")
            {
                std::wstring key{};
                GetXmlAttribute(child, "ID", key);
                bool AsSeparator{ false };
                GetXmlAttribute(child, "AsSeparator", AsSeparator);
                if (!AsSeparator)
                {
                    // 创建下拉菜单下的按钮
                    auto buttonDef = RibbonUi::Get().FindButtonDefinition(key);
                    if (buttonDef.key == key)
                    {
                        //注意下拉菜单要提供有效的默认值
                        if (!opMenuButton) {
                            opMenuButton = ICommandButtonMenuDefinition::Create(
                                id.c_str(),
                                buttonDef.key.c_str(),
                                menuDef.caption,
                                menuDef.iconDirectory,
                                menuDef.tooltip
                            );
                        }
                        if (opMenuButton)
                        {
                            opMenuButton->AddItem(
                                ICommandMenuItemDefinition::Create(key.c_str(), key.c_str(),
                                    buttonDef.caption, buttonDef.tooltip, buttonDef.iconDirectory)
                            );
                        }
                    }
                }
            }
        }
        // 将下拉菜单添加至group
        if (opMenuButton) {
            pGroup->AddButtonMenu(TransferOwnership(opMenuButton));
        }
    }

    CommandButton FindButtonDefinition(const std::wstring& cmdId)
    {
        RibbonUi& ribbonUi = RibbonUi::Get();
        return ribbonUi.FindButtonDefinition(cmdId);
    }

}

